#include <algorithm>
#include <functional>
#include <AL/al.h>
#include <OgreOSMScene.h>


#include "include/TextureTest.h"
#include "include/NetManager.h"
#include "include/App.h"
#include "include/Scripting.h"
#include "include/DotScene.h"
#include "include/Gui.h"

using namespace Ogre;
using namespace Ei;

App* App::theApp = NULL;

// constructor destructor and setup* {{{
App::App(Ei::Scripting* scr,
	 const char * scene_fname) :
	mScripting(scr)
{
	eilog("starting the App");
	// assert(theApp == NULL)
	theApp = this;

	mStatsOn = true;
	mNumScreenShots = 0;
	mTimeUntilNextToggle = 0;

	// split scene path into dir and filename
	// XXX use boost path
	String path(scene_fname), dir_name, file_name;
	size_t last_slash =  path.find_last_of("\\/");
	if (last_slash != String::npos) {
		dir_name = path.substr(0, last_slash);
		file_name = path.substr(last_slash + 1);
	} else {
		dir_name = String(".");
		file_name = path;
	}

	Ogre::Root * root = new Ogre::Root("", "", "Ogre.log");
	setupResources(dir_name);
	setupRenderSystem(root);
	root->initialise(false);
	root->addFrameListener(this);

	// create scene manager
	mSceneMgr = root->createSceneManager(ST_GENERIC); 


	
	setupMainWindow(root);
	// XXX ?
	TextureManager::getSingleton().setDefaultNumMipmaps(5);

	// this must be here (XXX why?)
	Ogre::ResourceGroupManager::getSingleton().initialiseAllResourceGroups();

	// XXX will be replaced by OIS i guess
//	mInputDevice = PlatformManager::getSingleton().createInputReader();
//	mInputDevice->initialise(mWindow, true, true);
	eilog("input OK");
	mDebugOverlay = OverlayManager::getSingleton().
		getByName("Core/DebugOverlay");
	showDebugOverlay(true);	
	
	// Load the scene 
	loadScene(file_name, root);
	
	
	// create light Sphere
        if (mSceneMgr->hasLight("Omni01"))
        {
		SceneNode * rootnode = mSceneMgr->getRootSceneNode()->createChildSceneNode("lightbol");
        	Entity* mEntity = mSceneMgr->createEntity("lightSphere", "bol.mesh");
        	rootnode->setPosition(mSceneMgr->getSceneNode("Omni01")->getPosition()); 
        	rootnode->attachObject(mEntity);
	}
	

	// setup shadows	
	mSceneMgr->setShadowTechnique(SHADOWTYPE_NONE);
	mSceneMgr->setShadowTextureSettings(1024, 2);
	mSceneMgr->setShadowFarDistance(10000);
        mSceneMgr->setShadowColour(ColourValue(0.5, 0.5, 0.5));
        mSceneMgr->setAmbientLight(ColourValue(0.0, 0.0, 0.0));
	
	
	setupCamera();
	// Setup network
	mNetMgr = new Ei::NetManager();
	mNetMgr->SetupMethods();
	
	// setup Gui, incoporates the event manager
	mGui = new Gui(mSceneMgr, mWindow);
	root->addFrameListener(mGui);
	
	// add lightmap texture
	mSceneMgr->getEntity("plane")->setMaterialName("LightmapBlender");
	
	// assign aphablend to pilars
	for(int i = 0 ; App::sceneMgr()->hasEntity("pilaar" + StringConverter::toString(i)) ; i ++)
	{
	  mSceneMgr->getEntity("pilaar" + StringConverter::toString(i))->setMaterialName("PaalBlender");
	}
	// assign wall blend to wall's
	for(int i = 0 ; App::sceneMgr()->hasEntity("muur" + StringConverter::toString(i)) ; i ++)
	{
	  mSceneMgr->getEntity("muur" + StringConverter::toString(i))->setMaterialName("MuurBlender");
	}
	
	
	// set camera to nice position
	mSceneMgr->getSceneNode("Camera")->setPosition(Vector3(8.96979, 24.2523, -23.2043));
	mSceneMgr->getSceneNode("Camera_PitchNode")->setOrientation(Quaternion(0.916365, -0.40035, 0, 0));
	mSceneMgr->getSceneNode("Camera")->setOrientation(Quaternion(0.00279254, 0, -0.999998, 0));
	
	// add overlay
	
	// add a compositor
	//CompositorManager::getSingleton().addCompositor(mWindow->getViewport(0) , "TuneColor");
	//CompositorManager::getSingleton().setCompositorEnabled(mWindow->getViewport(0), "TuneColor", true);


	// start rendering
	eilog("shadowtype = " + StringConverter::toString(mSceneMgr->getShadowTechnique()));
	eilog("start rendering");
	root->startRendering();  
	
	
	  
}

App::~App()
{

//	PlatformManager::getSingleton().destroyInputReader( mInputDevice );
	delete mGui;
	// XXX killall life
}

void App::loadScene(Ogre::String file_name, Ogre::Root * root)
{
	String ext(file_name, file_name.rfind('.'));
	
	if (ext == ".OSM") {
		// load the scene
		OSMScene oScene;
		oScene.initialise(file_name.c_str(), NULL);
		oScene.createScene();
		mSceneMgr = oScene.getSceneManager();
	} else {
		// XXX try other (e.g. exterior close)?
		mSceneMgr = root->createSceneManager(ST_GENERIC); 
		DotSceneLoader dotScene(NULL);
		dotScene.parseDotScene(file_name, "General", mSceneMgr);
	}
	
}

void App::setupResources(String dir_name)
{
	Ogre::ResourceGroupManager & resMan = 
		Ogre::ResourceGroupManager::getSingleton();
	resMan.addResourceLocation(MAIN_RESOURCES_DIR, "FileSystem", "General" );
	
	resMan.addResourceLocation(MAIN_RESOURCES_DIR "/media/shaders", 
				   "FileSystem", "General");
	resMan.addResourceLocation(MAIN_RESOURCES_DIR "/media/gui", 
				   "FileSystem", "General");
				   			   
       
				   
				   
	resMan.addResourceLocation(dir_name, "FileSystem", "General");
}

void App::setupRenderSystem(Ogre::Root * root)
{
	String rs_name(mScripting->getTableCString("win", "renderSystem"));
	if (rs_name.find("Direct3D") != String::npos 
	    || rs_name.find("D3D") != String::npos) {
		rs_name = "Direct3D9 Rendering Subsystem";
		root->loadPlugin("RenderSystem_Direct3D9");
	} else { // default to OpenGL 
		rs_name = "OpenGL Rendering Subsystem";
		root->loadPlugin("RenderSystem_GL");
	}
	RenderSystem * rs = root->getRenderSystemByName(rs_name);
	if (!rs) {
		// XXX where is grace?
		eilog("Can't load rendering system: %s\n", rs_name.c_str());
		exit(1);
	}
	root->setRenderSystem(rs);
}



void App::setupMainWindow(Ogre::Root * root)
{
	NameValuePairList opts;
	mScripting->tableToPairs("win", &opts);
	mWindow = root->
		createRenderWindow("Excersize in Immerstion IV",
				   mScripting->getTableInt("win", "width"),
				   mScripting->getTableInt("win", "height"),
				   mScripting->getTableBool("win", "fullscreen"),
				   &opts);
}

void App::setupCamera()
{

	eilog("creating camera");
	
	SceneNode * cam_node = mSceneMgr->createSceneNode("Camera");
	mSceneMgr->getRootSceneNode()->addChild(cam_node);	
	
	SceneNode * cam_nodeheading = mSceneMgr->createSceneNode("Camera_Heading");
	cam_node->addChild(cam_nodeheading);
	
	SceneNode * cam_nodepitch = mSceneMgr->createSceneNode("Camera_PitchNode");
	cam_nodeheading->addChild(cam_nodepitch);
	
	SceneNode * cam_trans = mSceneMgr->createSceneNode("Camera_TransNode");
	cam_nodepitch->addChild(cam_trans);
		
	Camera * cam = mSceneMgr->createCamera("Mono camera");

	cam->setPosition(0,0,0);
	cam->setDirection(0,0,-1); 
	cam->setNearClipDistance(0.1); // changed near clipping to allow user fog mesh
	cam->setFarClipDistance(100000);
	cam->setAutoAspectRatio(true);
	cam_trans->attachObject(cam);
	Viewport* vp = mWindow->addViewport(cam);
	vp->setBackgroundColour(ColourValue(.5, .9, .5));
	
	// set up headlight
	
	Light* headlight = mSceneMgr->createLight("headlight");
	headlight->setType(Light::LT_POINT);
	headlight->setDiffuseColour( 1, 1, 1 );
        headlight->setSpecularColour( 1, 1, 1.0 );
        //headlight->setSpotlightRange( Degree(80), Degree(10) );
	
	//headlight->setDirection(Vector3(0,0,-1));
//	headlight->setAttenuation(8000,1,0.0005,0);
	headlight->setCastShadows(true);
	
	//headlight->setPosition(true);
	//headlight->setDirection(true);
	
	 
	
	
}


void App::setupLook()
{
//	mSceneMgr->setShadowTechnique(SHADOWTYPE_NONE);
}

void App::CreateTestTextureObject()
{

	// create the scenenode
     	SceneNode * mBaseNode = mSceneMgr->getRootSceneNode()->createChildSceneNode("TestCubeNode");
     	mBaseNode->scale(2,2,2);
     	mBaseNode->setPosition(0,2,0);
    
    	 // create the meshes
    	Entity * TestCube  = mSceneMgr->createEntity("TM_Mesh01", "UserFog.mesh");

    	 // attach the objects to node to render them
     	mBaseNode->attachObject(TestCube); 
     	
    	 // set default material to use
     	TestCube->setMaterialName(App::scripting()->getTableCString("material", "TestMaterial"));
     //	mSceneMgr->getEntity("Ground Floor")->setMaterialName(App::scripting()->getTableCString("material", "Plane"));
     	
     	eilog("used material : " + String(App::scripting()->getTableCString("material", "TestMaterial")));
     	eilog("created entity");
     
     	
}
// }}}

bool App::processUnbufferedKeyInput(const FrameEvent& evt)
{
	// ------( misc )----------------------------------------------------
	if( mGui->getEventProcessor()->getInputReader()->isKeyDown( KC_ESCAPE) ) {
		return false;
	}

				  
        if (mGui->getEventProcessor()->getInputReader()->isKeyDown(KC_SYSRQ) && mTimeUntilNextToggle <= 0) {
		char tmp[20];
		sprintf(tmp, "screenshot_%d.png", ++mNumScreenShots);
		mWindow->writeContentsToFile(tmp);
		mTimeUntilNextToggle = 0.5;
		mWindow->setDebugText(String("Wrote ") + tmp);
	}
	

        
	if (mGui->getEventProcessor()->getInputReader()->isKeyDown(KC_P)&& mTimeUntilNextToggle <= 0 ) 
	{                           


	   eilog("pos " + StringConverter::toString(mSceneMgr->getSceneNode("Camera")->getPosition()) );
	   eilog("orient PN " + StringConverter::toString( mSceneMgr->getSceneNode("Camera_PitchNode")->getOrientation() ) );
	   eilog("orient HN " + StringConverter::toString( mSceneMgr->getSceneNode("Camera")->getOrientation() ) );
	   mTimeUntilNextToggle = .5;

	}
	static bool toggle = true;
	if (mGui->getEventProcessor()->getInputReader()->isKeyDown(KC_TAB)&& mTimeUntilNextToggle <= 0 ) 
	{                           
	 if(toggle)
	 {
		App::sceneMgr()->getEntity("plane")->setMaterialName("LightmapBlender");		
	 }
	 else
	 {
		App::sceneMgr()->getEntity("plane")->setMaterialName("red");
		eilog("RED");
	 }
	toggle= !toggle;
	   
	   mTimeUntilNextToggle = .5;

	}
	
        // Return true to continue rendering        
        return true;
}

bool App::processUnbufferedMouseInput(const FrameEvent& evt)
{

	
/*	if (mInputDevice->getMouseButton(0))
	{
	// blend material usingMouseX
	blendMatTwo = blendMatTwo +  (mInputDevice->getMouseRelativeX() * 0.001);
	blendMatOne = blendMatOne - (mInputDevice->getMouseRelativeX()* 0.003);
	blendState = blendMatOne;
	
	static_cast<MaterialPtr> (MaterialManager::getSingleton( ).getByName("Vloer2"))
        ->getTechnique(0)->getPass(0)->getTextureUnitState("VloerMix1")-> 
        setColourOperationEx(LBX_BLEND_MANUAL, 
			     LBS_TEXTURE, 
			     LBS_CURRENT, 
			     ColourValue(1, 0, 0),
			     ColourValue(1, 0, 0), 
			     blendMatTwo);
			     
	static_cast<MaterialPtr> (MaterialManager::getSingleton( ).getByName("Object2"))
        ->getTechnique(0)->getPass(0)->getTextureUnitState("ObjectMix1")-> 
        setColourOperationEx(LBX_BLEND_MANUAL, 
			     LBS_TEXTURE, 
			     LBS_CURRENT, 
			     ColourValue(1, 0, 0),
			     ColourValue(1, 0, 0), 
			     blendMatTwo);
			     
	
	static_cast<MaterialPtr> (MaterialManager::getSingleton( ).getByName("Vloer2"))
        ->getTechnique(0)->getPass(0)->getTextureUnitState("VloerMix")-> 
        setColourOperationEx(LBX_BLEND_MANUAL, 
			     LBS_TEXTURE, 
			     LBS_CURRENT, 
			     ColourValue(1, 0, 0),
			     ColourValue(1, 0, 0), 
			     blendMatOne);
			     
	static_cast<MaterialPtr> (MaterialManager::getSingleton( ).getByName("Object2"))
        ->getTechnique(0)->getPass(0)->getTextureUnitState("ObjectMix")-> 
        setColourOperationEx(LBX_BLEND_MANUAL, 
			     LBS_TEXTURE, 
			     LBS_CURRENT, 
			     ColourValue(1, 0, 0),
			     ColourValue(1, 0, 0), 
			     blendMatOne);
				
	}
	//else
	//{
	mSceneMgr->getSceneNode("Camera")
		->yaw(Degree(-mInputDevice->getMouseRelativeX() * 0.2));
	mSceneMgr->getSceneNode("Camera_PitchNode")
		->pitch(Degree(-mInputDevice->getMouseRelativeY() * 0.2));
	//}*/
	return true;
}

void App::showDebugOverlay(bool show)
{
	if (mDebugOverlay) {
		if (show) {
			mDebugOverlay->show();
		} else {
			mDebugOverlay->hide();
		}
	}
}

// XXX we stink!!! 
void App::postWorldStep()
{

}

bool App::frameStarted(const FrameEvent& evt)
{
	static Real currentTime = 0;

	// We update all loaded animations each frame
	SceneManager::AnimationIterator animationIt =
		mSceneMgr->getAnimationIterator();

	while(animationIt.hasMoreElements()) {
		Animation* animation = animationIt.getNext();

		const Animation::NodeTrackList& trackList =
			animation->_getNodeTrackList();

		Animation::NodeTrackList::const_iterator it =
			trackList.begin();
		Animation::NodeTrackList::const_iterator iend =
			trackList.end();

		for(; it != iend; ++it) {
			const NodeAnimationTrack* track =
				it->second;
			track->getAssociatedNode()
				->resetToInitialState();
		}

		currentTime += evt.timeSinceLastFrame;
		animation->apply(currentTime);
	}

	// Call default
	if(mWindow->isClosed())
		return false;

//	mInputDevice->capture();
	mNetMgr->pollSocket(1.0);
	if (mTimeUntilNextToggle >= 0)
		mTimeUntilNextToggle -= evt.timeSinceLastFrame;

	
	// XXX all updates should be done via this signal!
//	updateSig(evt.timeSinceLastFrame);

// blend materials


/// where do I get the correct pointer to the materaial
// OGRE_AUTO_SHARED_MUTEX Ogre::SharedPtr<Material*>::SharedPtr(); ;
// mMaterial->bind();

// mMaterial  = MaterialManager::getSingleton().getByName( "Ei4/TextureTest" );
  
// mMaterial->getTechnique(0)->getPass(0)->getTextureUnitState("Blend")-> 
//        setColourOperationEx(LBX_BLEND_MANUAL, LBS_TEXTURE, LBS_CURRENT, 1,1, blendState); 

	     // move headlight
	     
	     Quaternion orient = App::sceneMgr()->getCamera("Mono camera")->getDerivedOrientation();
	     mSceneMgr->getLight("headlight")->setDirection(orient.zAxis() * -1);	     
	     Vector3 trans(100,100,100);
	     trans = orient * trans;
	     mSceneMgr->getLight("headlight")->setPosition( mSceneMgr->getSceneNode("Camera")->getPosition() + trans);
		
             return (processUnbufferedKeyInput(evt) && processUnbufferedMouseInput(evt));
}

bool App::frameEnded(const FrameEvent& evt)
{
	//Real time = evt.timeSinceLastFrame;

	updateStats();
	return true;
}

void App::updateStats(void)
{
	OverlayManager * om = &OverlayManager::getSingleton();

	// update stats when necessary
	const RenderTarget::FrameStats& stats = mWindow->getStatistics();
	om->getOverlayElement("Ei/CurrFps")->
		setCaption("Current FPS: " +
			   StringConverter::toString( stats.lastFPS ));

	om->getOverlayElement("Ei/AverageFps")->
		setCaption("Avg. FPS: " +
			   StringConverter::toString( stats.avgFPS ));
	
	om->getOverlayElement("Ei/Triangles")->
		setCaption("Material Num: " +
			   StringConverter::toString(materialNum));

	om->getOverlayElement("Ei/Snacks")->
		setCaption("BlendState: " +
			   StringConverter::toString( blendState )); 

}

void App::createAthena()
{
	
	  // Set ambient light off
        mSceneMgr->setAmbientLight(ColourValue(0.0, 0.0, 0.0));

	Light* mSunLight;
	// Light* mLight;
        // Fixed light, dim
        mSunLight = mSceneMgr->createLight("SunLight");
        mSunLight->setType(Light::LT_SPOTLIGHT);
        mSunLight->setPosition(1000,1250,500);
        mSunLight->setSpotlightRange(Degree(30), Degree(50));
        Vector3 dir;
        dir = -mSunLight->getPosition();
        dir.normalise();
        mSunLight->setDirection(dir);
        mSunLight->setDiffuseColour(0.35, 0.35, 0.38);
        mSunLight->setSpecularColour(0.9, 0.9, 1);

      
        
	SceneNode* node;
 	Entity* mAthene;
        node = mSceneMgr->getRootSceneNode()->createChildSceneNode();
        mAthene = mSceneMgr->createEntity( "athene", "athene.mesh" );
        mAthene->setMaterialName("red");
        node->attachObject( mAthene );
        node->translate(0,-20, 0);
        node->yaw(Degree(90));

        Entity* pEnt;

        node = mSceneMgr->getRootSceneNode()->createChildSceneNode();
        pEnt = mSceneMgr->createEntity( "col1", "column.mesh" );
        pEnt->setMaterialName("blue");
        node->attachObject( pEnt );
        node->translate(200,0, -200);

        node = mSceneMgr->getRootSceneNode()->createChildSceneNode();
        pEnt = mSceneMgr->createEntity( "col2", "column.mesh" );
        pEnt->setMaterialName("blue");
        node->attachObject( pEnt );
        node->translate(200,0, 200);

        node = mSceneMgr->getRootSceneNode()->createChildSceneNode();
        pEnt = mSceneMgr->createEntity( "col3", "column.mesh" );
        pEnt->setMaterialName("blue");
        node->attachObject( pEnt );
        node->translate(-200,0, -200);

        node = mSceneMgr->getRootSceneNode()->createChildSceneNode();
        pEnt = mSceneMgr->createEntity( "col4", "column.mesh" );
        pEnt->setMaterialName("blue");
        node->attachObject( pEnt );
        node->translate(-200,0, 200);
       

        // Floor plane
        Plane plane;
        plane.normal = Vector3::UNIT_Y;
        plane.d = 100;
        MeshManager::getSingleton().createPlane("Myplane",
            ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, plane,
            1500,1500,50,50,true,1,5,5,Vector3::UNIT_Z);
        Entity* pPlaneEnt = mSceneMgr->createEntity( "plane", "Myplane" );
        pPlaneEnt->setMaterialName("LightmapBlender");
        pPlaneEnt->setCastShadows(false);
        mSceneMgr->getRootSceneNode()->createChildSceneNode()->attachObject(pPlaneEnt);
        
}
